上一篇已經學會如何宣告陣列、使用索引存取元素,並搭配迴圈處理全部資料。這一篇要繼續往下理解:陣列為什麼能透過索引快速找到元素?陣列名稱又為什麼和指標有關?
這些語法表面上看起來不太一樣,幕後其實都和同一件事有關:陣列元素在記憶體中的排列方式。
陣列不只是把資料放在同一個名稱底下,它還會讓所有元素在記憶體中一個接一個排列,中間不會穿插其他資料。這就是所謂的連續記憶體空間。
可以寫一支程式,實際印出陣列中每個元素的數值與記憶體位址:
#include <stdio.h>
int main(void) {
int scores[5] = {80, 90, 75, 60, 85};
for (int i = 0; i < 5; i++) {
printf("scores[%d] = %d,記憶體位址:%p\n",
i,
scores[i],
(void *)&scores[i]);
}
return 0;
}
%p 是 printf() 用來輸出記憶體位址的格式,(void *) 則把位址轉成 %p 預期接收的通用指標型態。
某一次執行的輸出如下:
scores[0] = 80,記憶體位址:0x16dafa5a0
scores[1] = 90,記憶體位址:0x16dafa5a4
scores[2] = 75,記憶體位址:0x16dafa5a8
scores[3] = 60,記憶體位址:0x16dafa5ac
scores[4] = 85,記憶體位址:0x16dafa5b0
記憶體位址每次執行時可能不同,但這次的結果清楚顯示,位址尾數依序是 a0、a4、a8、ac、b0,每次都增加 4 bytes。這表示這個環境中的 int 占用 4 bytes,也驗證了陣列元素會連續存放。
因為排列方式很規律,電腦可以利用下面的概念直接算出某個元素的位置:
元素位址 = 陣列起始位址 + 索引 × 每個元素的大小
所以 scores[3] 的位址
= 0x16dafa5a0 + 3 × 4 bytes
= 0x16dafa5a0 + 12 bytes
= 0x16dafa5ac
所以,無論要讀取第一個元素還是第一千個元素,只要知道索引,就可以直接找到它,不需要從頭逐一尋找。使用索引存取陣列元素的時間複雜度是 O(1)。
陣列與指標是 C 語言中關係非常密切的兩個觀念。以下面的陣列為例:
int scores[5] = {80, 90, 75, 60, 85};
在大多數運算式中,陣列名稱 scores 會自動轉換成指向第一個元素的指標,因此下面的比較結果會成立:
scores == &scores[0]
& 是取址運算子,所以 &scores[0] 代表「取得第一個元素的記憶體位址」。
因此,下面兩種寫法會取得相同的元素:
scores[2]
*(scores + 2)
這裡的 * 是解參照運算子,用來透過記憶體位址取出裡面的數值。因此兩種寫法得到的都是 75:
scores[i] 等價於 *(scores + i)

這裡的 scores + 2 不是把記憶體位址單純加上 2 bytes。因為 scores 指向 int,指標加上 2 時,會往後跨過兩個 int 元素的距離。假設一個 int 占用 4 bytes,實際上就是往後移動 8 bytes,到達 scores[2] 的位置。
可以先把陣列名稱想成:它固定在第一個元素的位置。我們不能讓 scores 自己往後移:
scores++; // 錯誤
不過,陣列裡面的資料還是可以修改:
scores[0] = 100; // 正確
所以,陣列不是指標;只是在很多情況下,陣列名稱會被當成第一個元素的位址使用。現在先記住這點就可以了。
如果要讓函式處理陣列,可以把陣列名稱傳給函式:
#include <stdio.h>
void printScores(int scores[], int length) {
for (int i = 0; i < length; i++) {
printf("%d ", scores[i]);
}
printf("\n");
}
int main(void) {
int scores[5] = {80, 90, 75, 60, 85};
printScores(scores, 5);
return 0;
}
在函式的參數中,int scores[] 會被視為 int *scores,也就是指向第一個元素的指標。函式無法只靠這個指標知道陣列共有幾個元素,因此通常還要另外傳入陣列長度,例如上面的 length。
陣列的元素會連續存放在記憶體中,因此電腦可以根據起始位址、索引與元素大小,直接計算出指定元素的位置。這也是陣列能在 O(1) 時間內存取指定元素的原因。
今日重點:
陣列的元素會連續存放在記憶體中。
相鄰元素的位址差距,等於一個元素所占的空間。
取址運算子 & 用來取得位址,解參照運算子 * 用來透過位址取值。
陣列名稱在大多數運算式中會轉換成第一個元素的位址,但陣列本身不是指標。
使用 scores[i] 與 *(scores + i) 可以取得同一個元素。
將陣列傳入函式時,通常需要另外傳入元素數量。
看懂陣列在記憶體中的樣子後,下一篇我們就繼續觀察:為什麼陣列能快速存取元素,但在中間插入或刪除資料時,卻需要大家一起「搬家」。